<?php

class TrackerController extends AtomController {
	public $defaultAction = 'index';
	private $announce = array();
	private $_torrent = null;
	private $user = null;


	public function filters() {
		return array(
			array('application.filters.RbacExFilter - index, announce, scrape'),
		);
	}


	public function actionIndex() {
		$this->redirect('torrent');
	}

	public function actionRegister() {
		Yii::app()->user->setState('action', 'register');
		$this->redirect('torrent');
	}

	public function actionLogin() {
		Yii::app()->user->setState('action', '');
		$this->redirect('torrent');
	}

	public function actionLogout() {
		Yii::app()->user->logout();
		$this->redirect(Yii::app()->homeUrl);
	}

	private function sendErr($msg) {
		$this->sendBencResp(array('failure reason' => array('type' => 'string', 'value' => $msg)));
		die();
	}

	private function sendBencResp($d) {
		$this->sendBencRespRaw(Benc::encode(array('type' => 'dictionary', 'value' => $d)));
	}

	private function sendBencRespRaw($x) {
		header('Content-Type: text/plain');
		header('Pragma: no-cache');
		print($x);
	}

	private function getAnnounceParams() {
		$ip = '';
		$event = '';
		$compact = 0;
		$req = 'passkey:info_hash:peer_id:!ip:port:uploaded:downloaded:left:!event:!compact';
		foreach (explode(':', $req) as $x) {
			if ($x[0] == '!') {
				$x = substr($x, 1);
				$opt = true;
			}
			else
				$opt = false;
			if (!isset($_GET[$x])) {
				if (!$opt)
					$this->sendErr("missing key '$x'");
				continue;
			}
			$$x = $_GET[$x];
		}

		foreach (array('info_hash','peer_id') as $x) {
			if (strlen($_GET[$x]) != 20)
				$this->sendErr("invalid $x (" . strlen($_GET[$x]) . " - " . urlencode($_GET[$x]) . ")");
		}

		if (get_magic_quotes_gpc()) {
			$info_hash = bin2hex(stripslashes($info_hash));
			$peer_id = bin2hex(stripslashes($peer_id));
		} else {
			$info_hash = bin2hex($info_hash);
			$peer_id = bin2hex($peer_id);
		}

		$rsize = 50;
		foreach(array('num want', 'numwant', 'num_want') as $k) {
			if (isset($_GET[$k])) {
				$rsize = 0 + $_GET[$k];
				break;
			}
		}

		$this->user = User::model()->findByAttributes(array('passkey' => $passkey));
		if ($this->user == null)
			$this->sendErr("User with passkey '$passkey' is not registered on this tracker");

		$this->announce = array(
			'passkey'		=> $passkey,
			'info_hash'		=> $info_hash,
			'peer_id'		=> $peer_id,
			'ip'			=> addslashes(Misc::getip()),
			'port'			=> 0 + $port,
			'uploaded'		=> 0 + $uploaded,
			'downloaded'	=> 0 + $downloaded,
			'left'			=> 0 + $left,
			'event'			=> $event,
			'rsize'			=> $rsize,
			'agent'			=> $_SERVER['HTTP_USER_AGENT'],
			'seeder'		=> ($left == 0) ? 'yes' : 'no',
			'user_id'		=> $this->user->id,
			'compact'		=> 0 + $compact,
		);

	}

	private function getRequestedTorrent() {
		$torrent = Torrent::model()->findByAttributes( array('info_hash' => $this->announce['info_hash']) );

		if ($torrent === null)
			$this->sendErr('Torrent is not registered with this tracker');

		$this->_torrent = $torrent;
	}

	private function getRandomPeers() {

		$peers = Peer::model()->
			random($this->announce['rsize'])->
			findAllByAttributes(array('torrent_id' => $this->_torrent->id));

		$resp = "d" . Benc::benc_str('interval') . 'i' . 1800 . 'e' . Benc::benc_str('peers');

		if ($this->announce['compact'] == 1) {
			$p='';
			foreach($peers as $peer) {
				$p = str_pad(pack("Nn", ip2long($peer->ip), $peer->port), 6);
			}
			$resp .= strlen($p).':'.$p;
		}
		else {
			$resp .= 'l';
			foreach($peers as $peer) {
				$resp .= "d" .
					Benc::benc_str('ip') . Benc::benc_str($peer->ip) .
					Benc::benc_str('peer id') . Benc::benc_str(pack('H*', $peer->peer_id)) .
					Benc::benc_str('port') . Benc::benc_int($peer->port) .
					"e";
			}
			$resp .= 'e';
		}
		$resp .= 'e';
		return $resp;
	}

	private function getPeer($create = true) {
		$peer = Peer::model()->findByAttributes(
			array('torrent_id' => $this->_torrent->id, 'user_id' => $this->announce['user_id']));

		if ($peer === null) {
			if ($create) $peer = new Peer();
			else $this->sendErr('You are not in peer list!');
		}

		if ($peer->isNewRecord)
			$peer->torrent_id = $this->_torrent->id;
		$this->announce['was_seeder'] = $peer->seeder;
		$peer->attributes = $this->announce;
		$peer->last_announce = null;

		return $peer;

	}

	private function updateUserStats() {
		$this->user->downloaded +=
			floor((float) $this->_torrent->download_coefficient * $this->announce['downloaded']);

		if ($this->_torrent->uploader_id !== $this->user->id)
			$this->user->uploaded += $this->announce['uploaded'];
		else
			$this->user->uploaded +=
				floor((float) Yii::app()->params['settings']['uploader_coefficient'] * $this->announce['uploaded']);
		$this->user->save();
	}

	private function updateTorrentRecord() {
		if (!$this->_torrent->save()) {
			$this->sendErr('Can\'t update record on event "'.$this->announce['event'].'"');
		}
	}

	private function updatePeers() {
		$seeders = $this->_torrent->seeders;
		$leechers= $this->_torrent->leechers;
		$times_completed = $this->_torrent->times_completed;

		switch ($this->announce['event']) {
			case 'started':
				$peer = $this->getPeer();

				if ($peer->save()) {
					if ($peer->isSeeder())
						$this->_torrent->seeders = ++$seeders;
					else
						$this->_torrent->leechers = ++$leechers;
					$this->updateTorrentRecord();
				}
				break;

			case 'stopped':
				$peer = $this->getPeer(false);
				if ($peer !== null) {
					if ($peer->isSeeder())
							$this->_torrent->seeders = max(--$seeders, 0);
						else
							$this->_torrent->leechers = max(--$leechers, 0);
					$this->updateTorrentRecord();
					$peer->delete();
				}
				break;

			case 'completed':
				$this->_torrent->times_completed = ++$times_completed;
			case '':
				$peer = $this->getPeer();

				if ($peer->save()) {
					if ($peer->isSeeder() && $this->announce['was_seeder'] !== 'yes') {
						$this->_torrent->seeders = (++$seeders);
						$this->_torrent->leechers = max(--$leechers, 0);
						$this->updateTorrentRecord();
					}
				}
				break;

			default:
				$this->sendErr('Invalid event from client');

		}

	}

	public function actionAnnounce() {
		$resp = '';
		try {
			$this->getAnnounceParams();
			$this->getRequestedTorrent();
			$this->updatePeers();
			$this->updateUserStats();

			$resp .= $this->getRandomPeers();
			$this->sendBencRespRaw($resp);

		} catch (Exception $e) { $this->sendErr($e->getTraceAsString()); }

	}

	public function actionScrape() {
		$r = 'd' . Benc::benc_str('files') . 'd';

		$fields = 'info_hash, seeders, leechers, times_completed';

		if (!isset($_GET["info_hash"]))
			$query = "SELECT $fields FROM torrents ORDER BY info_hash";
		else {
			$info_hash = (get_magic_quotes_gpc()) ? bin2hex(stripslashes($_GET['info_hash'])): bin2hex($_GET['info_hash']);
			$query = "SELECT $fields FROM torrents WHERE info_hash = '$info_hash'";
		}

		$rows = Yii::app()->db->createCommand($query)->queryAll();

		foreach($rows as $row) {
			$r .= Benc::benc_str(pack("H*", $row['info_hash'])) . 'd' .
				Benc::benc_str('complete') . Benc::benc_int($row['seeders']) .
				Benc::benc_str('downloaded') . Benc::benc_int($row['times_completed']).
				Benc::benc_str('incomplete') . Benc::benc_int($row['leechers']) .
				'e';
		}

		$r .= 'ee';

		$this->sendBencRespRaw($r);

	}

}

?>
